/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	Frustum.cpp
*
*	Comments	-	See Frustum.h
*
**************************************************************************************/
#include "../Include/Frustum.h"

namespace Pulse
{
	Frustum::Frustum( void )
	{

	}

	Frustum::Frustum( const Matrix4x4 &view, const Matrix4x4 &projection )
	{
		Update( view, projection );
	}

	Frustum::Frustum( const Matrix4x4 &view, FLOAT fov, FLOAT aspect, FLOAT _near, FLOAT _far )
	{
		Update( view, fov, aspect, _near, _far );
	}

	void Frustum::Update( const Matrix4x4 &view, const Matrix4x4 &projection )
	{
		// Build a combined view & projection matrix
		Matrix4x4 m = view * projection;

		// Left clipping plane
		m_planes[EFrustumSide::LEFT].a = -(m._14 + m._11);
		m_planes[EFrustumSide::LEFT].b = -(m._24 + m._21);
		m_planes[EFrustumSide::LEFT].c = -(m._34 + m._31);
		m_planes[EFrustumSide::LEFT].d = -(m._44 + m._41);

		// EFrustumSide::RIGHT clipping plane
		m_planes[EFrustumSide::RIGHT].a = -(m._14 - m._11);
		m_planes[EFrustumSide::RIGHT].b = -(m._24 - m._21);
		m_planes[EFrustumSide::RIGHT].c = -(m._34 - m._31);
		m_planes[EFrustumSide::RIGHT].d = -(m._44 - m._41);

		// EFrustumSide::TOP clipping plane
		m_planes[EFrustumSide::TOP].a = -(m._14 - m._12);
		m_planes[EFrustumSide::TOP].b = -(m._24 - m._22);
		m_planes[EFrustumSide::TOP].c = -(m._34 - m._32);
		m_planes[EFrustumSide::TOP].d = -(m._44 - m._42);

		// EFrustumSide::BOTTOM clipping plane
		m_planes[EFrustumSide::BOTTOM].a = -(m._14 + m._12);
		m_planes[EFrustumSide::BOTTOM].b = -(m._24 + m._22);
		m_planes[EFrustumSide::BOTTOM].c = -(m._34 + m._32);
		m_planes[EFrustumSide::BOTTOM].d = -(m._44 + m._42);

		// EFrustumSide::_NEAR clipping plane
		m_planes[EFrustumSide::_NEAR].a = -(m._13);
		m_planes[EFrustumSide::_NEAR].b = -(m._23);
		m_planes[EFrustumSide::_NEAR].c = -(m._33);
		m_planes[EFrustumSide::_NEAR].d = -(m._43);

		// EFrustumSide::_FAR clipping plane
		m_planes[EFrustumSide::_FAR].a = -(m._14 - m._13);
		m_planes[EFrustumSide::_FAR].b = -(m._24 - m._23);
		m_planes[EFrustumSide::_FAR].c = -(m._34 - m._33);
		m_planes[EFrustumSide::_FAR].d = -(m._44 - m._43);

		// Normalize the planes
		for ( SIZE_T i = 0; i < 6; i++ )
		{
			//D3DXPlaneNormalize( &Planes[i], &Planes[i] );
			m_planes[i].Normalize();
		}
		// Recompute the 8 corner points
		RecomputePoints();
	}

	void Frustum::Update( const Matrix4x4 &view, FLOAT fov, FLOAT aspect, FLOAT _near, FLOAT _far )
	{
		// Precompute required values.
		FLOAT fYScale = 1.0f/tanf(fov/2.0f);
		FLOAT fXScale = fYScale / aspect;

		// EFrustumSide::LEFT clipping plane
		m_planes[EFrustumSide::LEFT].a = -(view._13 + view._11 * fXScale);
		m_planes[EFrustumSide::LEFT].b = -(view._23 + view._21 * fXScale);
		m_planes[EFrustumSide::LEFT].c = -(view._33 + view._31 * fXScale);
		m_planes[EFrustumSide::LEFT].d = -(view._43 + view._41 * fXScale);

		// EFrustumSide::RIGHT clipping plane
		m_planes[EFrustumSide::RIGHT].a = -(view._13 - view._11 * fXScale);
		m_planes[EFrustumSide::RIGHT].b = -(view._23 - view._21 * fXScale);
		m_planes[EFrustumSide::RIGHT].c = -(view._33 - view._31 * fXScale);
		m_planes[EFrustumSide::RIGHT].d = -(view._43 - view._41 * fXScale);

		// EFrustumSide::TOP clipping plane
		m_planes[EFrustumSide::TOP].a = -(view._13 - view._12 * fYScale);
		m_planes[EFrustumSide::TOP].b = -(view._23 - view._22 * fYScale);
		m_planes[EFrustumSide::TOP].c = -(view._33 - view._32 * fYScale);
		m_planes[EFrustumSide::TOP].d = -(view._43 - view._42 * fYScale);

		// EFrustumSide::BOTTOM clipping plane
		m_planes[EFrustumSide::BOTTOM].a = -(view._13 + view._12 * fYScale);
		m_planes[EFrustumSide::BOTTOM].b = -(view._23 + view._22 * fYScale);
		m_planes[EFrustumSide::BOTTOM].c = -(view._33 + view._32 * fYScale);
		m_planes[EFrustumSide::BOTTOM].d = -(view._43 + view._42 * fYScale);

		// EFrustumSide::_NEAR clipping plane
		m_planes[EFrustumSide::_NEAR].a = -view._13;
		m_planes[EFrustumSide::_NEAR].b = -view._23;
		m_planes[EFrustumSide::_NEAR].c = -view._33;
		m_planes[EFrustumSide::_NEAR].d = -(view._43 - _near);

		// EFrustumSide::_FAR clipping plane
		m_planes[EFrustumSide::_FAR].a = view._13;
		m_planes[EFrustumSide::_FAR].b = view._23;
		m_planes[EFrustumSide::_FAR].c = view._33;
		m_planes[EFrustumSide::_FAR].d = (view._43 - _far);

		// Normalize the planes
		for ( ULONG i = 0; i < 6; i++ )
		{
			//D3DXPlaneNormalize( &Planes[i], &Planes[i] );
			m_planes[i].Normalize();
		}

		// Recompute the 8 corner points
		RecomputePoints();
	}

	void Frustum::RecomputePoints( void )
	{
		// Compute the 8 corner points
		for ( int i = 0; i < 8; ++i )
		{
			const Plane& p0 = (i & 1) ? m_planes[EFrustumSide::_NEAR] : m_planes[EFrustumSide::_FAR];
			const Plane& p1 = (i & 2) ? m_planes[EFrustumSide::TOP]  : m_planes[EFrustumSide::BOTTOM];
			const Plane& p2 = (i & 4) ? m_planes[EFrustumSide::LEFT] : m_planes[EFrustumSide::RIGHT];

			// Compute the point at which the three planes intersect
			const Vector3 n0( p0.a, p0.b, p0.c );
			const Vector3 n1( p1.a, p1.b, p1.c );
			const Vector3 n2( p2.a, p2.b, p2.c );
			Vector3 n1_n2, n2_n0, n0_n1;  

			//D3DXVec3Cross( &n1_n2, &n1, &n2 );
			n1_n2 = n1.CrossProduct( n2 );

			//D3DXVec3Cross( &n2_n0, &n2, &n0 );
			n2_n0 = n2.CrossProduct( n0 );

			//D3DXVec3Cross( &n0_n1, &n0, &n1 );
			n0_n1 = n0.CrossProduct( n1 );

			//const float cosTheta = D3DXVec3Dot( &n0, &n1_n2 );
			const FLOAT cosTheta = n0.DotProduct( n1_n2 );
			//const float secTheta = 1.f / cosTheta;
			const FLOAT secTheta = 1.0f / cosTheta;

			n1_n2 = n1_n2 * p0.d;
			n2_n0 = n2_n0 * p1.d;
			n0_n1 = n0_n1 * p2.d;

			m_points[i] = -(n1_n2 + n2_n0 + n0_n1) * secTheta;

		} // Next Corner
	}

	BOOL Frustum::TestSphere( const Vector3 &pos, const FLOAT radius ) const
	{
		// Test frustum planes
		for ( ULONG i = 0; i < 6; ++i )
		{
			//float fDot = D3DXPlaneDotCoord( &Planes[i], &vecCenter );
			float fDot = m_planes[i].DistanceToPoint( &pos );

			// Sphere entirely in front of plane
			if ( fDot >= radius )
				return FALSE;

		} // Next Plane

		// Intersects
		return TRUE;
	}

}